package com.example.mybatisdemo.interceptor;

import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
import com.example.mybatisdemo.enums.TableUpdateEnum;
import com.example.mybatisdemo.pojo.User;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.*;

/**
 * @ClassName SqlInterceptor
 * @Description TODO
 * @Author xiaoying
 * @Date 2023/7/26 16:24
 * @Version V1.0.0
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}
        )
})
public class SqlInterceptor implements Interceptor {

    private final JdbcTemplate jdbcTemplate;
    private final ApplicationContext applicationContext;

    public SqlInterceptor(JdbcTemplate jdbcTemplate, ApplicationContext applicationContext) {
        this.jdbcTemplate = jdbcTemplate;
        this.applicationContext = applicationContext;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        monitorSql(invocation);
        // Proceed with the update statement execution
        return invocation.proceed();
    }


    private void monitorSql(Invocation invocation) {
        try {

            // Get the MappedStatement and BoundSql from the invocation
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameterObject = invocation.getArgs()[1];
            BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
            if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
                // Retrieve the SQL and the SET fields with their values
                String sql = boundSql.getSql();
                Collection<String> tables = new TableNameParser(sql).tables();
                if (!CollectionUtils.isEmpty(tables)) {
                    String tableName = tables.iterator().next();
                    TableUpdateEnum byTableName = TableUpdateEnum.getByTableName(tableName);
                    if (null != byTableName) {
                        // 注意sql关键词的大小写，默认updateById update方法都是大写
                        Map<String, Object> map = extractSetFieldsWithValues(byTableName, sql, boundSql.getParameterObject());
                        if(map.isEmpty()){
                            System.out.println("未过滤出关注字段");
                            return;
                        }
                        String whereSql = extractWhereValues(sql, boundSql.getParameterObject());
                        if(StringUtils.isBlank(whereSql)){
                            System.out.println("未获取到where条件");
                            return;
                        }
                        List<Map<String, Object>> recordList = jdbcTemplate.queryForList("select * from " + tableName + " " + whereSql);
                        if(CollectionUtils.isNotEmpty(recordList)){
                            for (Map<String, Object> record : recordList) {
                                for (Map.Entry<String, Object> stringStringEntry : map.entrySet()) {
                                    Object oldValue = record.get(stringStringEntry.getKey());
                                    // 数据的id主键
                                    System.out.println("oldValue:"+oldValue+" newValue:"+stringStringEntry.getValue());
                                    if (!stringStringEntry.getValue().equals(oldValue)) {
                                         // TODO 字段变化了，做自己业务逻辑处理
                                        break;
                                    }
                                }
                                return;
                            }
                        }
                    }
                }

            }

        } catch (Exception e) {
           e.printStackTrace();
        }
    }

    private Map<String, Object> extractSetFieldsWithValues(TableUpdateEnum byTableName, String sql, Object parameterObject) throws  IllegalAccessException {
        // Here you can implement your logic to extract the SET fields and their values from the SQL statement.
        // This is just a basic example and may not cover all possible cases.
        String[] changeField = byTableName.getChangeField();
        List<String> changeFieldList = Arrays.asList(changeField);
        Map<String, Object> setFieldsWithValues = new HashMap<>();
        int setIndex = sql.indexOf("SET");
        int whereIndex = sql.indexOf("WHERE");

        if (setIndex != -1 && whereIndex != -1) {
            String setClause = sql.substring(setIndex + 3, whereIndex).trim();
            String[] setFields = setClause.split(",");
            for (String setField : setFields) {
                String[] parts = setField.trim().split("=");
                String fieldName = parts[0].trim();
                if (changeFieldList.contains(fieldName)) {
                    Object fieldValue = getParameterValue(parameterObject, fieldName);
                    if(null != fieldValue){
                        setFieldsWithValues.put(fieldName, fieldValue);
                    }
                }
            }
        }

        return setFieldsWithValues;
    }

    private String extractWhereValues(String sql, Object parameterObject) throws IllegalAccessException {

        int whereIndex = sql.indexOf("WHERE");
        StringBuffer sb = new StringBuffer();
        sb.append("WHERE 1=1");
        if (whereIndex != -1) {
            String setClause = sql.substring(whereIndex + 5).trim();
            String[] setFields = setClause.split("AND");
            for (String setField : setFields) {
                String[] parts = setField.trim().split("=");
                String fieldName = parts[0].trim();
                if (parts[1].trim().equals("?")) {
                    Object fieldValue = getParameterValue(parameterObject, fieldName);
                    if(null == fieldValue){
                        return null;
                    }
                    sb.append(" AND ");
                    sb.append(fieldName).append("=").append(fieldValue);
                } else {
                    sb.append(" AND ").append(setField);
                }

            }
        }

        return sb.toString();
    }

    private Object getParameterValue(Object parameterObject, String fieldName) throws IllegalAccessException {
        // This method retrieves the value of the specified field from the parameterObject.
        // You may need to enhance this method to handle nested properties, if required.
        if (parameterObject instanceof Map) {
            Map map = (Map) parameterObject;
            //updateById(et), update(et, wrapper);
            Object et = map.getOrDefault(Constants.ENTITY, null);
            if (et != null) {
                TableInfo tableInfo = TableInfoHelper.getTableInfo(et.getClass());
                if (fieldName.equals("id")) {
                    if (et instanceof User) {
                        User user = (User) et;
                        return user.getId();
                    }
                } else {
                    List<TableFieldInfo> fieldList = tableInfo.getFieldList();
                    for (TableFieldInfo tableFieldInfo : fieldList) {
                        if (tableFieldInfo.getColumn().equals(fieldName)) {
                            return tableFieldInfo.getField().get(et);
                        }
                    }
                }
            }
        }
        return null;
    }

}
